package org.axed.user.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;

/**
 * Displays a soft caret (by div) on any element implementing CaretAble
 */
public class CaretBase {
	private final static boolean DEBUG_EXPANDER = false;

	/**
	 * Timer for blinking
	 */
	private Timer timer;

	/**
	 * The div of the caret.
	 */
	private Element caretDiv;

	/**
	 * The div of the beam.
	 */	
	private Element beamDiv = null;

	/**
	 * The expander is a hidden div moved infront of the caret, so it
	 * does not bump into the right border.
	 */	
	private Element expanderDiv;

	/**
	 * true if the blinking caret is currently black
	 */
	private boolean blink = true;

	/**
	 * true if the caret is to be shown at all.
	 * If for example the axed widget has no focus, the caret is not to be shown.
	 */
	protected boolean shown = false;

	/**
	 * True if the caret div is displayed. For example the caret can be shown, 
	 * but scrolled out of the vieware, in this case shown == true, but
	 * display == false.
	 */
	private boolean caretDisplay = false;

	/**
	 * True if the beam div is displayed.
	 */
	private boolean beamDisplay = false;

	/**
	 * The coordinates of the caret in the widget.
	 */
	Coords coords = new Coords();

	/**
	 * The widget this belongs to.
	 */
	CaretWidget host;

	/**
	 * the position of the caret.
	 */
	int left = -1;
	int top = -1;

	/**
	 * Constructor.
	 */
	public CaretBase(CaretWidget host, boolean beam) {
		this.host = host;
	
		caretDiv = DOM.createDiv();
		DOM.setStyleAttribute(caretDiv, "position", "absolute");
		DOM.setStyleAttribute(caretDiv, "width", "1px");
		DOM.setStyleAttribute(caretDiv, "height", "10px");
		DOM.setStyleAttribute(caretDiv, "background", "black");
		DOM.setStyleAttribute(caretDiv, "zIndex", "10");
		DOM.appendChild(host.getElement(), caretDiv);

		if (beam) {
			beamDiv = DOM.createDiv();
			DOM.setStyleAttribute(beamDiv, "position", "absolute");
			DOM.setStyleAttribute(beamDiv, "width", "100%");
			DOM.setStyleAttribute(beamDiv, "height", "100px");
			DOM.setStyleAttribute(beamDiv, "background", "#FFFFAD");
			DOM.setStyleAttribute(beamDiv, "zIndex", "0");
			DOM.appendChild(host.getElement(), beamDiv);
		}

		expanderDiv = DOM.createDiv();
		DOM.setStyleAttribute(expanderDiv, "position", "absolute");
		DOM.setStyleAttribute(expanderDiv, "width", "0");
		DOM.setStyleAttribute(expanderDiv, "height", "0");
		DOM.setStyleAttribute(expanderDiv, "width", "1px");
		DOM.setStyleAttribute(expanderDiv, "height", "1px");
		DOM.setStyleAttribute(expanderDiv, "background", "transparent");
		if (DEBUG_EXPANDER) {
			DOM.setStyleAttribute(expanderDiv, "background", "blue");
		}
		DOM.setStyleAttribute(expanderDiv, "zIndex", "0");
		DOM.appendChild(host.getScrollDiv(), expanderDiv);

		timer = new Timer() {
			public void run() {
				if (blink) {
					DOM.setStyleAttribute(caretDiv, "background", "white");
					blink = false;
				} else {
					DOM.setStyleAttribute(caretDiv, "background", "black");
					blink = true;
				}
			}
		};
		timer.scheduleRepeating(750);
	}

	/**
	 * Returns the coords relative to the widget.
	 * Note: do not alter the returned object, its still used when scrolling.
	 */
	public Coords getCoords() {
		return coords;
	}

	/**
	 * Hides the caret.
	 */
	public void hide() {
		if (!shown) {
			return;
		}
		timer.cancel();
		setDisplay(false, false);
		blink = false;
		shown = false;
	}

	public boolean isShown() {
		return shown;
	}

	/**
	 * Scrolls the caret into view.
	 */
	public void scrollIntoView() {
		//This otherwhise handy GWT function is on purpose not used, because its performance
		//doesn't meet the requirements here.
		//DOM.scrollIntoView(caretDiv);

		if (coords.top < host.getScrollTop()) {
			host.setScrollTop(coords.top);
		}
		if (coords.top + coords.height > host.getScrollBottom()) {
			host.setScrollBottom(coords.top + coords.height);
		}
		if (coords.left - AxedArea.borderLeft < host.getScrollLeft()) {
			host.setScrollLeft(coords.left - AxedArea.borderLeft);
		}
		if (coords.left + AxedArea.borderRight > host.getScrollRight()) {
			host.setScrollRight(coords.left + AxedArea.borderRight);
		}
	}

	/**
	 * Shows the caret.
	 */
	public void show() {
		if (shown) {
			return;
		}
		setDisplay(true, true);
		DOM.setStyleAttribute(caretDiv, "background", "black");
		blink = true;
		shown = true;
		timer.scheduleRepeating(750);
	}

	/**
 	 * Moves the caret.
 	 */
	public void position() {
		// first turn the carret black, while its moving
		timer.cancel();
		DOM.setStyleAttribute(caretDiv, "background", "black");
		blink = true;
		
		host.getCursorCoords(coords);
		
		// scroll the axedwidget
		setCaretDiv();
		scrollIntoView();
		timer.scheduleRepeating(750);
	}

//privates:
	/**
	 * Positions the caret div.
	 */
	protected void setCaretDiv() {
		left = coords.left; 
		top  = coords.top + 1;
		int height = coords.height;
		int scrollBottom = host.getScrollBottom();
		int scrollTop    = host.getScrollTop();
		int scrollLeft   = host.getScrollLeft();
		int scrollRight  = host.getScrollRight();
		
		if (top > scrollBottom || top + height < scrollTop) {
			// caret is up or down in invisible area -> caret and beam are invisible
			setDisplay(false, false);
		} else if (left < scrollLeft || left - 2 > scrollRight) {
			// caret is in invisible area
			setDisplay(false, true);
		} else if (top <= scrollBottom && top + height >= scrollBottom) {
			// caret is clipped on the bottom
			setDisplay(true, true);
			height = scrollBottom - top;
		} else if (top < scrollTop && top + height >= scrollTop) {
			// caret is clipped on top
			setDisplay(true, true);
			height -= scrollTop - top - 1;
			top = scrollTop + 2;
			//TODO
		} else {
			// caret is normally in view area
			setDisplay(true, true);
		}

		left -= scrollLeft; 
		top  -= scrollTop;

		if (beamDiv != null) {
			DOM.setStyleAttribute(beamDiv, "left", 0 + "px");
			DOM.setStyleAttribute(beamDiv, "width", (host.getClientWidth())+ "px");
			DOM.setStyleAttribute(beamDiv, "top",  top + "px");
			DOM.setStyleAttribute(beamDiv, "height", height + "px");
		}

		DOM.setStyleAttribute(caretDiv, "left", left + "px");
		DOM.setStyleAttribute(caretDiv, "top",  top + "px");
		DOM.setStyleAttribute(caretDiv, "height", height + "px");

		DOM.setStyleAttribute(expanderDiv, "left", (left + AxedArea.borderRight + scrollLeft - 1) + "px");
		DOM.setStyleAttribute(expanderDiv, "top",  (top + scrollTop) + "px");
	}
	
	/**
	 * Sets the carets and beam divs inline or none.
	 * This routine will not bother the browser if already set the way.
	 *
	 * @param setDisplay    true ... set divs inline, false ... set divs none.
	 *
	 */
	private void setDisplay(boolean setCaretDisplay, boolean setBeamDisplay) {
		if (caretDisplay && !setCaretDisplay) {
			DOM.setStyleAttribute(caretDiv, "display", "none");
			caretDisplay = false;
		} else if (!caretDisplay && setCaretDisplay) {
			DOM.setStyleAttribute(caretDiv, "display", "inline");
			caretDisplay = true;
		}

		if (beamDiv == null) {
			return;
		}
		if (beamDisplay && !setBeamDisplay) {
			DOM.setStyleAttribute(beamDiv, "display", "none");
			beamDisplay = false;
		} else if (!beamDisplay && setBeamDisplay) {
			DOM.setStyleAttribute(beamDiv, "display", "inline");
			beamDisplay = true;
		}
	}

}
